13  등분산 검정

Keywords

python, 전처리, 통계, 가설검정, 기계학습, 회귀, 분류, 군집, 모델 학습, 모델 평가

등분산성(Homoscedasticity)은 여러 집단의 분산이 동일하다는 가정으로, 많은 통계 검정의 전제 조건이다. 등분산 검정(Test for Homogeneity of Variance)은 이 가정이 타당한지 확인하는 과정이다. 정규성 검정과 마찬가지로, 등분산성 가정이 위배되면 t-검정이나 ANOVA의 결과가 부정확해질 수 있다. 이 장에서는 시각적 방법과 통계적 검정을 통해 등분산성을 평가하고, 등분산성이 위배될 때의 대응 전략을 학습한다.

예제: 데이터 로드

import seaborn as sns
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

# 데이터 로드
df = sns.load_dataset("penguins").dropna()

print("데이터 크기:", df.shape)
print("\n그룹 변수:")
print(df.select_dtypes(include=['object', 'category']).columns.tolist())
print("\n연속형 변수:")
print(df.select_dtypes(include=[np.number]).columns.tolist())
데이터 크기: (333, 7)

그룹 변수:
['species', 'island', 'sex']

연속형 변수:
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']

13.1 등분산성의 개념

등분산성(Homoscedasticity)은 여러 집단 또는 조건에서 종속 변수의 분산이 동일하다는 가정이다. 수학적으로는 다음과 같이 표현한다.

\[ \sigma_1^2 = \sigma_2^2 = \cdots = \sigma_k^2 \]

여기서 σᵢ²는 i번째 집단의 모분산이다.

등분산성 vs 이분산성

개념 정의 특징 영향
등분산성 (Homoscedasticity) 모든 집단의 분산이 같음 σ₁² = σ₂² = ⋯ = σₖ² 표준 t-검정, ANOVA 사용 가능
이분산성 (Heteroscedasticity) 집단 간 분산이 다름 σ₁² ≠ σ₂² ≠ ⋯ ≠ σₖ² 검정력 저하, 1종 오류 증가

이분산성의 영향

  • 가설검정의 1종 오류율(α) 왜곡
  • 신뢰구간의 정확도 저하
  • 검정력(power) 감소
  • 회귀분석에서 계수 추정의 비효율성

예제: 집단별 분산 확인

# 종별 체중 분산 계산
print("=== 종별 체중 분산 ===")
variances = df.groupby("species")["body_mass_g"].var()
print(variances)

print(f"\n최대 분산: {variances.max():.2f}")
print(f"최소 분산: {variances.min():.2f}")
print(f"분산 비율: {variances.max() / variances.min():.2f}:1")
=== 종별 체중 분산 ===
species
Adelie       210332.427964
Chinstrap    147713.454785
Gentoo       251478.332859
Name: body_mass_g, dtype: float64

최대 분산: 251478.33
최소 분산: 147713.45
분산 비율: 1.70:1

일반적으로 최대 분산이 최소 분산의 2배 이상이면 등분산성을 의심해야 한다.

13.2 등분산성 검정의 필요성

등분산성 가정은 여러 통계 기법의 전제 조건이다.

통계 기법별 등분산성 필요 여부

분석 기법 등분산 필요 중요도 비고
Student t-test 필요 높음 등분산 가정 하에 설계됨
ANOVA 필요 매우 높음 F-통계량이 등분산 가정
선형 회귀 (잔차) 필요 매우 높음 이분산성이 계수 추정 왜곡
Welch t-test 불필요 - 이분산 허용
Welch ANOVA 불필요 - 이분산 허용
비모수 검정 불필요 - 분포 가정 없음
부트스트랩 불필요 - 재표본추출 기반

중요한 사실

정규성보다 등분산성 위반이 더 심각한 경우가 많다. 특히 표본 크기가 집단 간 불균형할 때 이분산성은 1종 오류율을 크게 증가시킨다.

13.3 등분산성 검정 시점

등분산성 검정은 다음 시점에 수행한다.

검정 시점

  1. 집단 간 평균 비교 전: t-검정이나 ANOVA 수행 전 필수 확인
  2. ANOVA 적용 전: 다집단 비교 시 반드시 검정
  3. 회귀 분석의 잔차 진단: 예측값에 따른 잔차 분산 균일성 확인
  4. 반복측정 데이터: 시간이나 조건에 따른 분산 변화 확인

분석 흐름

데이터 로드
  ↓
정규성 검정 (각 그룹)
  ↓
등분산성 검정 ← 현재 장
  ↓
├─ 등분산 O → Student t-test / ANOVA
└─ 등분산 X → Welch t-test / Welch ANOVA / 비모수 검정

13.4 시각적 등분산성 점검

통계적 검정 전에 시각적으로 등분산성을 확인하는 것이 중요하다.

13.4.1 박스플롯

박스플롯은 집단 간 분산의 차이를 시각적으로 비교하는 가장 간단한 방법이다.

예제: 박스플롯으로 분산 비교

# 종별 체중 박스플롯
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 박스플롯
sns.boxplot(x="species", y="body_mass_g", data=df, ax=axes[0])
axes[0].set_title("Body Mass by Species")
axes[0].set_xlabel("Species")
axes[0].set_ylabel("Body Mass (g)")

# 바이올린 플롯 (분포 형태까지 확인)
sns.violinplot(x="species", y="body_mass_g", data=df, ax=axes[1])
axes[1].set_title("Body Mass Distribution by Species")
axes[1].set_xlabel("Species")
axes[1].set_ylabel("Body Mass (g)")

plt.tight_layout()
plt.show()

# 집단별 통계량
print("\n=== 종별 체중 통계량 ===")
summary = df.groupby("species")["body_mass_g"].agg(['mean', 'std', 'var', 'count'])
print(summary)


=== 종별 체중 통계량 ===
                  mean         std            var  count
species                                                 
Adelie     3706.164384  458.620135  210332.427964    146
Chinstrap  3733.088235  384.335081  147713.454785     68
Gentoo     5092.436975  501.476154  251478.332859    119

박스플롯 관찰 포인트

  • 박스 높이: IQR(사분위수 범위)을 나타내며, 집단 간 유사해야 함
  • 수염(whisker) 길이: 데이터의 전체 퍼짐 정도, 집단 간 비슷해야 함
  • 이상치 개수: 집단 간 이상치 비율이 크게 다르면 이분산 의심

13.4.2 분산 비교 히스토그램

집단별 분포를 겹쳐서 확인하면 퍼짐의 차이를 직접 비교할 수 있다.

예제: 히스토그램으로 분포 비교

# 종별 체중 분포
plt.figure(figsize=(10, 5))
sns.histplot(data=df, x="body_mass_g", hue="species", kde=True, alpha=0.5)
plt.title("Body Mass Distribution by Species")
plt.xlabel("Body Mass (g)")
plt.ylabel("Frequency")
plt.legend(title="Species")
plt.show()

분포의 퍼짐(너비)이 비슷하면 등분산성을 만족할 가능성이 높다.

13.4.3 잔차 플롯 (회귀분석)

회귀분석에서는 예측값에 대한 잔차 플롯으로 이분산성을 확인한다.

예제: 잔차 플롯

from sklearn.linear_model import LinearRegression

# 간단한 선형 회귀 (설명 목적)
X = df[["bill_length_mm"]].values
y = df["body_mass_g"].values

# 결측치 제거
mask = ~np.isnan(X).any(axis=1) & ~np.isnan(y)
X_clean = X[mask]
y_clean = y[mask]

# 회귀 모델
model = LinearRegression()
model.fit(X_clean, y_clean)
y_pred = model.predict(X_clean)
residuals = y_clean - y_pred

# 잔차 플롯
plt.figure(figsize=(10, 5))
plt.scatter(y_pred, residuals, alpha=0.5)
plt.axhline(y=0, color='red', linestyle='--', linewidth=2)
plt.title("Residual Plot")
plt.xlabel("Fitted Values")
plt.ylabel("Residuals")
plt.show()

print(f"잔차 평균: {residuals.mean():.4f}")
print(f"잔차 표준편차: {residuals.std():.2f}")

잔차 평균: -0.0000
잔차 표준편차: 649.48

잔차가 예측값에 관계없이 일정한 분산을 보이면 등분산성을 만족한다. 패턴(예: 깔때기 모양)이 보이면 이분산성을 의심해야 한다.

13.5 통계적 등분산 검정

시각적 방법을 보완하기 위해 통계적 검정을 수행한다.

13.5.1 주요 등분산 검정 비교

등분산 검정 방법 비교

검정 원리 장점 단점 전제 조건 권장 상황
Bartlett 로그 우도비 검정력 높음 (정규 분포 시) 정규성에 매우 민감 정규성 필요 정규분포 확실 시
Levene 평균 편차 안정적, 널리 사용 보통 검정력 정규성 불필요 일반적 상황 (권장)
Brown-Forsythe 중앙값 편차 이상치에 강건 약간 복잡 정규성 불필요 이상치 많을 때
Fligner-Killeen 순위 기반 매우 강건 검정력 낮음 정규성 불필요 비정규 분포

검정 선택 가이드

정규성 만족?
├─ Yes → Bartlett (검정력 우수)
└─ No  → Levene (가장 안정적, 실무 권장)
          ├─ 이상치 많음? → Brown-Forsythe
          └─ 극심한 비정규? → Fligner-Killeen

13.5.2 Levene 검정 (가장 권장)

Levene 검정은 각 관측값과 집단 평균(또는 중앙값)의 절대편차를 비교한다. 정규성에 민감하지 않아 실무에서 가장 널리 사용된다.

가설 설정

  • H₀ (귀무가설): 모든 집단의 분산이 같다 (등분산)
  • H₁ (대립가설): 적어도 하나의 집단의 분산이 다르다

예제: Levene 검정

from scipy.stats import levene

# 종별 체중 데이터 준비
groups = [
    df[df["species"] == sp]["body_mass_g"].dropna()
    for sp in df["species"].unique()
]

# Levene 검정
stat, p_value = levene(*groups)

print("=== Levene Test ===")
print(f"검정 통계량(W): {stat:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"\n유의수준 0.05 기준: ", end="")
if p_value > 0.05:
    print("등분산성 가정 가능 (H₀ 채택)")
else:
    print("등분산성 위배 (H₀ 기각, 이분산)")
=== Levene Test ===
검정 통계량(W): 5.1349
p-value: 0.0064

유의수준 0.05 기준: 등분산성 위배 (H₀ 기각, 이분산)

Levene 검정의 변형

# center 파라미터로 평균 vs 중앙값 선택 가능
# center='mean': 평균 기반 (기본값)
# center='median': 중앙값 기반 (Brown-Forsythe)
# center='trimmed': 절사평균 기반

stat_median, p_median = levene(*groups, center='median')
print(f"\nLevene (중앙값 기반, Brown-Forsythe): p = {p_median:.4f}")

Levene (중앙값 기반, Brown-Forsythe): p = 0.0064

13.5.3 Bartlett 검정

Bartlett 검정은 로그 우도비를 사용하며, 정규분포를 따를 때 가장 강력한 검정이다. 하지만 정규성에 매우 민감하여 실무에서는 잘 사용되지 않는다.

예제: Bartlett 검정

from scipy.stats import bartlett

# Bartlett 검정
stat, p_value = bartlett(*groups)

print("\n=== Bartlett Test ===")
print(f"검정 통계량(T): {stat:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"\n유의수준 0.05 기준: ", end="")
if p_value > 0.05:
    print("등분산성 가정 가능")
else:
    print("등분산성 위배")

print("\n⚠️ 주의: Bartlett 검정은 정규성에 민감함")
print("   정규성이 위배되면 잘못된 결과 (과도한 기각)")

=== Bartlett Test ===
검정 통계량(T): 5.6920
p-value: 0.0581

유의수준 0.05 기준: 등분산성 가정 가능

⚠️ 주의: Bartlett 검정은 정규성에 민감함
   정규성이 위배되면 잘못된 결과 (과도한 기각)

13.5.4 Fligner-Killeen 검정

Fligner-Killeen 검정은 순위 기반 비모수 검정으로, 극심한 비정규 분포나 두꺼운 꼬리를 가진 분포에서도 안정적이다.

예제: Fligner-Killeen 검정

from scipy.stats import fligner

# Fligner-Killeen 검정
stat, p_value = fligner(*groups)

print("\n=== Fligner-Killeen Test ===")
print(f"검정 통계량(H): {stat:.4f}")
print(f"p-value: {p_value:.4f}")
print(f"\n유의수준 0.05 기준: ", end="")
if p_value > 0.05:
    print("등분산성 가정 가능")
else:
    print("등분산성 위배")

print("\n✓ 장점: 비모수, 매우 강건")
print("  단점: 검정력이 다소 낮음")

=== Fligner-Killeen Test ===
검정 통계량(H): 9.2507
p-value: 0.0098

유의수준 0.05 기준: 등분산성 위배

✓ 장점: 비모수, 매우 강건
  단점: 검정력이 다소 낮음

13.5.5 검정 결과 종합 비교

예제: 모든 검정 결과 비교

# 모든 검정 수행
tests = {
    "Levene": levene(*groups),
    "Levene (median)": levene(*groups, center='median'),
    "Bartlett": bartlett(*groups),
    "Fligner-Killeen": fligner(*groups)
}

print("\n=== 등분산 검정 결과 요약 ===")
results = []
for name, (stat, p_val) in tests.items():
    results.append({
        "Test": name,
        "Statistic": round(stat, 4),
        "p-value": round(p_val, 4),
        "Result": "등분산" if p_val > 0.05 else "이분산"
    })

results_df = pd.DataFrame(results)
print(results_df.to_string(index=False))

=== 등분산 검정 결과 요약 ===
           Test  Statistic  p-value Result
         Levene     5.1349   0.0064    이분산
Levene (median)     5.1349   0.0064    이분산
       Bartlett     5.6920   0.0581    등분산
Fligner-Killeen     9.2507   0.0098    이분산

13.6 등분산성이 위배될 때의 대응 전략

등분산성 검정 결과가 유의하게 이분산이라면, 다음 전략 중 하나를 선택한다.

13.6.1 전략 1: 이분산 허용 검정 사용

등분산 가정을 필요로 하지 않는 대안 검정을 사용한다.

대안 검정

원래 검정 등분산 가정 대안 검정 등분산 불필요
Student t-test 필요 Welch t-test 불필요
One-way ANOVA 필요 Welch ANOVA 불필요
상관 분석 - 비모수 상관 (Spearman) 불필요
회귀 분석 필요 가중 최소제곱(WLS) 이분산 고려

예제: Student t-test vs Welch t-test

from scipy.stats import ttest_ind

# 두 종 선택
adelie = df[df["species"] == "Adelie"]["body_mass_g"].dropna()
gentoo = df[df["species"] == "Gentoo"]["body_mass_g"].dropna()

# Student t-test (등분산 가정)
t_equal, p_equal = ttest_ind(adelie, gentoo, equal_var=True)

# Welch t-test (이분산 허용)
t_welch, p_welch = ttest_ind(adelie, gentoo, equal_var=False)

print("=== t-test 비교: Adelie vs Gentoo ===")
print(f"\nStudent t-test (등분산 가정):")
print(f"  t = {t_equal:.4f}, p = {p_equal:.4f}")

print(f"\nWelch t-test (이분산 허용):")
print(f"  t = {t_welch:.4f}, p = {p_welch:.4f}")

print("\n→ 이분산인 경우 Welch t-test 사용 권장")
=== t-test 비교: Adelie vs Gentoo ===

Student t-test (등분산 가정):
  t = -23.4668, p = 0.0000

Welch t-test (이분산 허용):
  t = -23.2539, p = 0.0000

→ 이분산인 경우 Welch t-test 사용 권장

13.6.2 전략 2: 분포 변환

데이터를 변환하여 등분산성을 개선할 수 있다.

예제: 로그 변환 후 등분산 검정

# 로그 변환
df_log = df.copy()
df_log["log_body_mass"] = np.log(df["body_mass_g"])

# 변환 후 집단 데이터
groups_log = [
    df_log[df_log["species"] == sp]["log_body_mass"].dropna()
    for sp in df_log["species"].unique()
]

# 변환 전후 Levene 검정 비교
stat_orig, p_orig = levene(*groups)
stat_log, p_log = levene(*groups_log)

print("=== 변환 전후 Levene 검정 비교 ===")
print(f"원본 데이터: W = {stat_orig:.4f}, p = {p_orig:.4f}")
print(f"로그 변환: W = {stat_log:.4f}, p = {p_log:.4f}")

# 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

sns.boxplot(x="species", y="body_mass_g", data=df, ax=axes[0])
axes[0].set_title("Original Data")

sns.boxplot(x="species", y="log_body_mass", data=df_log, ax=axes[1])
axes[1].set_title("Log Transformed Data")

plt.tight_layout()
plt.show()
=== 변환 전후 Levene 검정 비교 ===
원본 데이터: W = 5.1349, p = 0.0064
로그 변환: W = 4.1210, p = 0.0171

주요 변환 방법

  • 로그 변환: 양의 왜도를 줄이고 분산 안정화
  • 제곱근 변환: 로그보다 약한 변환
  • Box-Cox 변환: 최적의 변환 파라미터 자동 추정
  • Yeo-Johnson 변환: 0과 음수 포함 데이터 가능

13.6.3 전략 3: 비모수 검정 사용

분포 가정을 하지 않는 비모수 검정을 사용한다.

예제: ANOVA vs Kruskal-Wallis

from scipy.stats import f_oneway, kruskal

# 종별 체중 데이터
species_groups = [
    df[df["species"] == sp]["body_mass_g"].dropna()
    for sp in df["species"].unique()
]

# 모수 검정 (ANOVA)
f_stat, p_anova = f_oneway(*species_groups)

# 비모수 검정 (Kruskal-Wallis)
h_stat, p_kw = kruskal(*species_groups)

print("=== 다집단 비교: 모수 vs 비모수 ===")
print(f"\nOne-way ANOVA (등분산 가정):")
print(f"  F = {f_stat:.4f}, p = {p_anova:.4f}")

print(f"\nKruskal-Wallis (비모수):")
print(f"  H = {h_stat:.4f}, p = {p_kw:.4f}")

print("\n→ 등분산성 위배 시 Kruskal-Wallis 사용")
=== 다집단 비교: 모수 vs 비모수 ===

One-way ANOVA (등분산 가정):
  F = 341.8949, p = 0.0000

Kruskal-Wallis (비모수):
  H = 212.0851, p = 0.0000

→ 등분산성 위배 시 Kruskal-Wallis 사용

13.6.4 전략 4: 가중 회귀 (회귀분석)

회귀분석에서 이분산성이 있을 때 가중 최소제곱(Weighted Least Squares)을 사용한다.

# 가중 최소제곱 예시 (개념적)
# from statsmodels.regression.linear_model import WLS
# weights = 1 / residuals**2  # 잔차의 역수로 가중치 계산
# model_wls = WLS(y, X, weights=weights).fit()

13.7 정규성과 등분산성의 관계

정규성과 등분산성은 독립적인 가정이다.

정규성 vs 등분산성

상황 정규성 등분산성 검정 선택
이상적 O O Student t-test, ANOVA
일반적 O X Welch t-test, Welch ANOVA
비정규 X O 비모수 검정 또는 변환 후 검정
최악 X X 비모수 검정 필수

검정 간 관계

  • Bartlett는 정규성에 의존: 정규성 위배 시 과도하게 기각
  • Levene과 Fligner는 정규성에 독립: 정규성과 무관하게 안정적

실무 권장 조합

정규성 검정: Q-Q plot + Shapiro-Wilk
     +
등분산 검정: 박스플롯 + Levene

예제: 정규성과 등분산성 동시 검정

print("=== 정규성 및 등분산성 검정 종합 ===\n")

# 각 종별 정규성 검정
for species in df["species"].unique():
    data = df[df["species"] == species]["body_mass_g"].dropna()
    stat, p_val = stats.shapiro(data)
    print(f"{species} 정규성: W = {stat:.4f}, p = {p_val:.4f} ", end="")
    print("→ 정규" if p_val > 0.05 else "→ 비정규")

# 등분산성 검정
stat, p_val = levene(*groups)
print(f"\n등분산성 (Levene): W = {stat:.4f}, p = {p_val:.4f} ", end="")
print("→ 등분산" if p_val > 0.05 else "→ 이분산")

# 최종 권장
print("\n=== 최종 권장 검정 ===")
if all([stats.shapiro(df[df["species"] == sp]["body_mass_g"].dropna())[1] > 0.05 
        for sp in df["species"].unique()]) and p_val > 0.05:
    print("✓ Student t-test / ANOVA 사용 가능")
elif p_val > 0.05:
    print("△ 정규성 위배 → 비모수 검정 또는 변환 후 검정")
else:
    print("✓ 이분산 → Welch t-test / Welch ANOVA 권장")
=== 정규성 및 등분산성 검정 종합 ===

Adelie 정규성: W = 0.9812, p = 0.0423 → 비정규
Chinstrap 정규성: W = 0.9845, p = 0.5605 → 정규
Gentoo 정규성: W = 0.9861, p = 0.2605 → 정규

등분산성 (Levene): W = 5.1349, p = 0.0064 → 이분산

=== 최종 권장 검정 ===
✓ 이분산 → Welch t-test / Welch ANOVA 권장

13.8 요약

이 장에서는 등분산 검정의 개념, 방법, 그리고 이분산 시 대응 전략을 학습했다. 주요 내용은 다음과 같다.

등분산 검정 방법 요약

방법 유형 장점 단점 권장 상황
박스플롯 시각적 직관적, 이상치 확인 주관적 초기 탐색
잔차 플롯 시각적 회귀 이분산 확인 회귀 전용 회귀분석
Levene 통계적 안정적, 널리 사용 보통 검정력 일반적 상황 (권장)
Bartlett 통계적 검정력 높음 정규성 민감 정규분포 확실 시
Fligner-Killeen 통계적 매우 강건 검정력 낮음 극심한 비정규

이분산 시 대응 전략

전략 방법 장점 단점 적용 상황
대안 검정 Welch t-test, Welch ANOVA 변환 불필요 검정력 약간 낮음 첫 번째 선택
분포 변환 로그, Box-Cox 등분산성 개선 해석 복잡 변환이 합리적인 경우
비모수 검정 Mann-Whitney, Kruskal-Wallis 분포 가정 불필요 검정력 낮음 정규성도 위배
가중 회귀 WLS 효율적 추정 구현 복잡 회귀분석

실무 점검 프로세스

  1. 시각적 확인: 박스플롯으로 분산 차이 확인
  2. 집단별 분산 계산: 최대/최소 비율 확인 (2배 이상 주의)
  3. 통계적 검정: Levene 검정 수행
  4. 정규성 고려: 정규성도 함께 확인
  5. 대응 전략 선택:
    • 등분산 O → Student t-test, ANOVA
    • 등분산 X → Welch 검정 또는 비모수 검정

주요 주의사항

  • 정규성보다 등분산성 위반이 더 심각할 수 있음
  • Bartlett는 정규성에 민감하므로 실무에서 비권장
  • Levene이 가장 안정적이고 널리 사용됨
  • 표본 크기가 집단 간 불균형하면 이분산성 영향 증가
  • 시각적 확인과 통계적 검정을 함께 고려

등분산 검정은 집단 간 비교 분석의 필수 단계이다. 시각적 방법과 통계적 검정을 종합적으로 활용하고, 이분산성이 확인되면 적절한 대안 검정이나 변환을 사용하는 것이 중요하다. 다음 장에서는 등분산성 가정을 바탕으로 한 t-검정과 ANOVA를 학습할 것이다.